/**
 * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

import { CardApi } from './../services/CardApi.js'
import moment from 'moment'
import Vue from 'vue'

const apiClient = new CardApi()

export default function cardModuleFactory() {
	return {
		state: {
			cards: [],
		},
		getters: {
			cardsByStack: (state, getters, rootState) => (id) => {
				return state.cards.filter((card) => {
					const { tags, users, due, unassigned, completed } = rootState.filter

					if (completed === 'open' && card.done !== null) {
						return false
					}
					if (completed === 'completed' && card.done == null) {
						return false
					}
					let allTagsMatch = true
					let allUsersMatch = true

					if (tags.length > 0) {
						tags.forEach((tag) => {
							if (card.labels.findIndex((l) => l.id === tag) === -1) {
								allTagsMatch = false
							}
						})
						if (!allTagsMatch) {
							return false
						}
					}

					if (users.length > 0) {
						users.forEach((user) => {
							if (!card?.assignedUsers || card.assignedUsers.findIndex((u) => u.participant.uid === user) === -1) {
								allUsersMatch = false
							}
						})
						if (!allUsersMatch) {
							return false
						}
					}

					if (unassigned && card.assignedUsers.length > 0) {
						return false
					}

					if (due !== '') {
						const datediffHour = ((new Date(card.duedate) - new Date()) / 3600 / 1000)
						switch (due) {
						case 'noDue':
							return (card.duedate === null)
						case 'overdue':
							return (card.overdue === 3)
						case 'dueToday':
							return (card.overdue >= 2)
						case 'dueWeek':
							return (datediffHour <= 7 * 24 && card.duedate !== null)
						case 'dueMonth':
							return (datediffHour <= 30 * 24 && card.duedate !== null)
						}
					}

					return true
				})
					.filter((card) => card.stackId === id)
					.filter((card) => {
						if (getters.getSearchQuery === '') {
							return true
						}

						let hasMatch = true
						const matches = getters.getSearchQuery.match(/(?:[^\s"]+|"[^"]*")+/g)

						const filterOutQuotes = (q) => {
							if (q[0] === '"' && q[q.length - 1] === '"') {
								return q.slice(1, -1)
							}
							return q
						}
						for (const match of matches) {
							let [filter, query] = match.indexOf(':') !== -1 ? match.split(/:(.*)/) : [null, match]
							const isEmptyQuery = typeof query === 'undefined' || filterOutQuotes(query) === ''

							if (filter === 'title') {
								if (isEmptyQuery) {
									continue
								}
								hasMatch = hasMatch && card.title.toLowerCase().includes(filterOutQuotes(query).toLowerCase())
							} else if (filter === 'description') {
								if (isEmptyQuery) {
									hasMatch = hasMatch && !!card.description
									continue
								}
								hasMatch = hasMatch && card.description.toLowerCase().includes(filterOutQuotes(query).toLowerCase())
							} else if (filter === 'list') {
								if (isEmptyQuery) {
									continue
								}
								const stack = getters.stackById(card.stackId)
								if (!stack) {
									return false
								}
								hasMatch = hasMatch && stack.title.toLowerCase().includes(filterOutQuotes(query).toLowerCase())
							} else if (filter === 'tag') {
								if (isEmptyQuery) {
									hasMatch = hasMatch && card.labels.length > 0
									continue
								}
								hasMatch = hasMatch && card.labels.findIndex((label) => label.title.toLowerCase().includes(filterOutQuotes(query).toLowerCase())) !== -1
							} else if (filter === 'date') {
								const datediffHour = ((new Date(card.duedate) - new Date()) / 3600 / 1000)
								query = filterOutQuotes(query)
								switch (query) {
								case 'overdue':
									hasMatch = hasMatch && (card.overdue === 3)
									break
								case 'today':
									hasMatch = hasMatch && (datediffHour > 0 && datediffHour <= 24 && card.duedate !== null)
									break
								case 'week':
									hasMatch = hasMatch && (datediffHour > 0 && datediffHour <= 7 * 24 && card.duedate !== null)
									break
								case 'month':
									hasMatch = hasMatch && (datediffHour > 0 && datediffHour <= 30 * 24 && card.duedate !== null)
									break
								case 'none':
									hasMatch = hasMatch && (card.duedate === null)
									break
								}

								if (card.duedate === null || !hasMatch) {
									return false
								}
								const comparator = query[0] + (query[1] === '=' ? '=' : '')
								const isValidComparator = ['<', '<=', '>', '>='].indexOf(comparator) !== -1
								const parsedCardDate = moment(card.duedate)
								const parsedDate = moment(query.slice(isValidComparator ? comparator.length : 0))
								switch (comparator) {
								case '<':
									hasMatch = hasMatch && parsedCardDate.isBefore(parsedDate)
									break
								case '<=':
									hasMatch = hasMatch && parsedCardDate.isSameOrBefore(parsedDate)
									break
								case '>':
									hasMatch = hasMatch && parsedCardDate.isAfter(parsedDate)
									break
								case '>=':
									hasMatch = hasMatch && parsedCardDate.isSameOrAfter(parsedDate)
									break
								default:
									hasMatch = hasMatch && parsedCardDate.isSame(parsedDate)
									break
								}

							} else if (filter === 'assigned') {
								if (isEmptyQuery) {
									hasMatch = hasMatch && card.assignedUsers.length > 0
									continue
								}
								hasMatch = hasMatch && card.assignedUsers.findIndex((assignment) => {
									return assignment.participant.primaryKey.toLowerCase() === filterOutQuotes(query).toLowerCase()
										|| assignment.participant.displayname.toLowerCase() === filterOutQuotes(query).toLowerCase()
								}) !== -1
							} else {
								hasMatch = hasMatch && (card.title.toLowerCase().includes(filterOutQuotes(match).toLowerCase())
									|| card.description.toLowerCase().includes(filterOutQuotes(match).toLowerCase()) || card.id === parseInt(filterOutQuotes(match)))
							}
							if (!hasMatch) {
								return false
							}
						}
						return true
					})
					.sort((a, b) => a.order - b.order || a.createdAt - b.createdAt)
			},
			cardById: state => (id) => {
				return state.cards.find((card) => card.id === id)
			},
		},
		mutations: {
			addCard(state, card) {
				card.labels = card.labels || []
				card.assignedUsers = card.assignedUsers || []
				const existingIndex = state.cards.findIndex(_card => _card.id === card.id)
				if (existingIndex !== -1) {
					const existingCard = state.cards[existingIndex]
					Vue.set(state.cards, existingIndex, Object.assign({}, existingCard, card))
				} else {
					state.cards.push(card)
				}
			},
			deleteCard(state, card) {
				const existingIndex = state.cards.findIndex(_card => _card.id === card.id)
				if (existingIndex !== -1) {
					state.cards.splice(existingIndex, 1)
				}
			},
			updateCard(state, card) {
				const existingIndex = state.cards.findIndex(_card => _card.id === card.id)
				if (existingIndex !== -1) {
					Vue.set(state.cards, existingIndex, Object.assign({}, state.cards[existingIndex], card))
				}
			},
			updateCardsReorder(state, cards) {
				for (const newCard of cards) {
					const existingIndex = state.cards.findIndex(_card => _card.id === newCard.id)
					if (existingIndex !== -1) {
						Vue.set(state.cards[existingIndex], 'order', newCard.order)
						Vue.set(state.cards[existingIndex], 'stackId', newCard.stackId)
					}
				}
			},
			assignCardToUser(state, user) {
				const existingIndex = state.cards.findIndex(_card => _card.id === user.cardId)
				if (existingIndex !== -1) {
					state.cards[existingIndex].assignedUsers.push(user)
				}
			},
			removeUserFromCard(state, user) {
				const existingIndex = state.cards.findIndex(_card => _card.id === user.cardId)
				if (existingIndex !== -1) {
					const foundIndex = state.cards[existingIndex].assignedUsers.findIndex(_user => _user.id === user.id)
					if (foundIndex !== -1) {
						state.cards[existingIndex].assignedUsers.splice(foundIndex, 1)
					}
				}
			},
			updateCardProperty(state, { card, property }) {
				const existingIndex = state.cards.findIndex(_card => _card.id === card.id)
				if (existingIndex !== -1) {
					Vue.set(state.cards[existingIndex], property, card[property])
					Vue.set(state.cards[existingIndex], 'lastModified', Date.now() / 1000)
				}
			},
			cardSetAttachmentCount(state, { cardId, count }) {
				const existingIndex = state.cards.findIndex(_card => _card.id === cardId)
				if (existingIndex !== -1) {
					Vue.set(state.cards[existingIndex], 'attachmentCount', count)
				}
			},
			cardIncreaseAttachmentCount(state, cardId) {
				const existingIndex = state.cards.findIndex(_card => _card.id === cardId)
				if (existingIndex !== -1) {
					Vue.set(state.cards[existingIndex], 'attachmentCount', state.cards[existingIndex].attachmentCount + 1)
				}
			},
			cardDecreaseAttachmentCount(state, cardId) {
				const existingIndex = state.cards.findIndex(_card => _card.id === cardId)
				if (existingIndex !== -1) {
					Vue.set(state.cards[existingIndex], 'attachmentCount', state.cards[existingIndex].attachmentCount - 1)
				}
			},
			addNewCard(state, card) {
				state.cards.push(card)
			},
			setCards(state, cards) {
				const deletedCards = state.cards.filter(_card => {
					return cards.findIndex(c => _card.id === c.id) === -1
				})
				for (const card of deletedCards) {
					this.commit('deleteCard', card)
				}
				for (const card of cards) {
					this.commit('addCard', card)
				}
			},
		},
		actions: {
			async cloneCard({ commit }, { cardId, targetStackId }) {
				const createdCard = await apiClient.cloneCard(cardId, targetStackId)
				commit('addCard', createdCard)
				return createdCard
			},
			async addCard({ commit }, card) {
				const createdCard = await apiClient.addCard(card)
				commit('addCard', createdCard)
				return createdCard
			},
			async updateCardTitle({ commit }, card) {
				const updatedCard = await apiClient.updateCard(card)
				commit('updateCardProperty', { property: 'title', card: updatedCard })
				commit('updateCardProperty', { property: 'referenceData', card: updatedCard })
			},
			async moveCard({ commit }, card) {
				const updatedCard = await apiClient.updateCard(card)
				commit('deleteCard', updatedCard)
			},
			async reorderCard({ commit, getters }, card) {
				let i = 0
				const newCards = []
				for (const c of getters.cardsByStack(card.stackId)) {
					if (c.id === card.id) {
						newCards.push(card)
					}
					if (i === card.order) {
						i++
					}
					if (c.id !== card.id) {
						newCards.push({ ...c, order: i++ })
					}
				}
				newCards.push(card)
				await commit('updateCardsReorder', newCards)

				apiClient.reorderCard(card).then((cards) => {
					commit('updateCardsReorder', Object.values(cards))
				})
			},
			async deleteCard({ commit }, card) {
				await apiClient.deleteCard(card.id)
				commit('deleteCard', card)
				commit('moveCardToTrash', card)
			},
			async archiveUnarchiveCard({ commit }, card) {
				let call = 'archiveCard'
				if (card.archived === false) {
					call = 'unArchiveCard'
				}

				const updatedCard = await apiClient[call](card)
				commit('updateCard', updatedCard)
			},
			async changeCardDoneStatus({ commit }, card) {
				let call = 'markCardAsDone'
				if (card.done === false) {
					call = 'markCardAsUndone'
				}

				const updatedCard = await apiClient[call](card)
				commit('updateCardProperty', { property: 'done', card: updatedCard })
			},
			async assignCardToUser({ commit }, { card, assignee }) {
				const user = await apiClient.assignUser(card.id, assignee.userId, assignee.type)
				commit('assignCardToUser', user)
			},
			async removeUserFromCard({ commit }, { card, assignee }) {
				const user = await apiClient.removeUser(card.id, assignee.userId, assignee.type)
				commit('removeUserFromCard', user)
			},
			async addLabel({ commit }, data) {
				await apiClient.assignLabelToCard(data)
				commit('updateCardProperty', { property: 'labels', card: data.card })
			},
			async removeLabel({ commit }, data) {
				await apiClient.removeLabelFromCard(data)
				commit('updateCardProperty', { property: 'labels', card: data.card })
			},
			async updateCardDesc({ commit }, card) {
				const updatedCard = await apiClient.updateCard(card)
				commit('updateCardProperty', { property: 'description', card: updatedCard })
			},
			async updateCardDue({ commit }, card) {
				const updatedCard = await apiClient.updateCard(card)
				commit('updateCardProperty', { property: 'duedate', card: updatedCard })
			},

			addCardData({ commit }, cardData) {
				const card = { ...cardData }
				commit('addStack', card.relatedStack)
				commit('addBoard', card.relatedBoard)
				delete card.relatedStack
				delete card.relatedBoard
				commit('addCard', card)
			},
		},
	}
}
